#include "frame_buffer.h"
#include "../../base/obj_cache.h"

OPEN_JLIB_NS


bool FrameBuffer::init (Texture* colorBuf) {
  if (!colorBuf || !colorBuf->valid())
    return false;
  clear();

  auto w = colorBuf->width();
  auto h = colorBuf->height();
  GLuint ofb, orb, fb = 0, rb = 0;
  glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&ofb);
  glGetIntegerv(GL_RENDERBUFFER_BINDING, (GLint*)&orb);

  glGenRenderbuffers(1, &rb);
  glBindRenderbuffer(GL_RENDERBUFFER, rb);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);

  glGenFramebuffers(1, &fb);
  glBindFramebuffer(GL_FRAMEBUFFER, fb);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuf->id(), 0);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rb);
  glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rb);

  auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  if (status != GL_FRAMEBUFFER_COMPLETE) {
    log_e("FrameBuffer: glCheckFramebufferStatus: %d", status);
    glBindRenderbuffer(GL_RENDERBUFFER, orb);
    glBindFramebuffer(GL_FRAMEBUFFER, ofb);
    glDeleteFramebuffers(1, &fb);
    glDeleteRenderbuffers(1, &rb);
    return false;
  }

  glBindRenderbuffer(GL_RENDERBUFFER, orb);
  glBindFramebuffer(GL_FRAMEBUFFER, ofb);
  _fb = fb;
  _rb = rb;
  return true;
}

bool FrameBuffer::init (Texture* colorBuf, Texture* depthBuf, Texture* stencilBuf) {
  if (!colorBuf || !colorBuf->valid())
    return false;
  if (!depthBuf || !depthBuf->valid())
    return false;
  if (stencilBuf && !stencilBuf->valid())
    return false;
  auto w = colorBuf->width();
  auto h = colorBuf->height();
  if (depthBuf->width() != w || depthBuf->height() != h)
    return false;
  if (stencilBuf && (stencilBuf->width() != w || stencilBuf->height() != h))
    return false;
  clear();

  GLuint ofb, fb;
  glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&ofb);

  glGenFramebuffers(1, &fb);
  glBindFramebuffer(GL_FRAMEBUFFER, fb);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuf->id(), 0);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthBuf->id(), 0);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, stencilBuf ? stencilBuf->id() : depthBuf->id(), 0);

  auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  if (status != GL_FRAMEBUFFER_COMPLETE) {
    log_e("FrameBuffer: glCheckFramebufferStatus: %d", status);
    glDeleteFramebuffers(1, &fb);
    glBindFramebuffer(GL_FRAMEBUFFER, ofb);
    return false;
  }

  glBindFramebuffer(GL_FRAMEBUFFER, ofb);
  _fb = fb;
  _colorBuf = colorBuf;
  _depthBuf = depthBuf;
  _stencilBuf = stencilBuf;
  return true;
}

bool FrameBuffer::drawable () {
  if (!valid())
    return false;
  GLuint fb;
  glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&fb);
  return _fb == fb;
}

void FrameBuffer::use () {
  debug_assert(valid());
  glPushAttrib(GL_VIEWPORT_BIT);
  glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&_ofb);
  glBindFramebuffer(GL_FRAMEBUFFER, _fb);
  glViewport(0, 0, width(), height());
}

void FrameBuffer::unuse () {
  debug_assert(drawable());
  glPopAttrib();
  glBindFramebuffer(GL_FRAMEBUFFER, _ofb);
}

void FrameBuffer::clear () {
  if (!valid())
    return;
  if (drawable())
    unuse();
  if (_rb)
    glDeleteRenderbuffers(1, &_rb);
  glDeleteFramebuffers(1, &_fb);
  _colorBuf = nullptr;
  _depthBuf = nullptr;
  _stencilBuf = nullptr;
  _rb = 0;
  _fb = 0;
}


CLOSE_JLIB_NS
